﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SharpSoft.Maths
{
    public class ColorHelper
    {
        public const float DefaultLightness = 2.0f;
        public const float DefaultChroma = 1.0f;
        private static float DistanceDivided(float a, float dividend)
        {
            return MathF.Pow(a / dividend, 2);
        }
        /// <summary>
        /// 计算色差
        /// </summary>
        /// <param name="aLab"></param>
        /// <param name="bLab"></param>
        /// <param name="_lightness"></param>
        /// <param name="_chroma"></param>
        /// <returns></returns>
        /// <remarks>
        /// <see cref="https://max.book118.com/html/2021/0920/5044323213004011.shtm"/>
        /// </remarks>
        public float ColorDiff_CMC(LAB aLab, LAB bLab, float _lightness = DefaultLightness, float _chroma = DefaultChroma)
        {
            var deltaL = aLab.L - bLab.L;
            var h = MathF.Atan2(aLab.B, aLab.A);

            var c1 = MathF.Sqrt(MathF.Pow(aLab.A, 2) + MathF.Pow(aLab.B, 2));
            var c2 = MathF.Sqrt(MathF.Pow(bLab.A, 2) + MathF.Pow(bLab.B, 2));
            var deltaC = c1 - c2;

            var deltaH = MathF.Sqrt(MathF.Pow(aLab.A - bLab.A, 2) + MathF.Pow(aLab.B - bLab.B, 2) - MathF.Pow(deltaC, 2));

            var t = 164f <= h || h >= 345f
                        ? .56f + MathF.Abs(.2f * MathF.Cos(h + 168.0f))
                        : .36f + MathF.Abs(.4f * MathF.Cos(h + 35.0f));
            var f = MathF.Sqrt(MathF.Pow(c1, 4) / (MathF.Pow(c1, 4) + 1900.0f));

            var sL = aLab.L < 16 ? .511f : (.040975f * aLab.L) / (1.0f + .01765f * aLab.L);
            var sC = (.0638f * c1) / (1 + .0131f * c1) + .638f;
            var sH = sC * (f * t + 1f - f);

            var differences = DistanceDivided(deltaL, _lightness * sL) +
                              DistanceDivided(deltaC, _chroma * sC) +
                              DistanceDivided(deltaH, sH);

            return MathF.Sqrt(differences);
        }
        private static float Distance(float a, float b)
        {
            return MathF.Pow(a - b, 2);
        }
        /// <summary>
        /// CIE1976算法计算色差
        /// </summary>
        /// <param name="aLab"></param>
        /// <param name="bLab"></param>
        /// <returns></returns>
        /// <remarks>
        /// <see cref="https://www.labcognition.com/onlinehelp/en/cie_1976_l_a_b__color_model.htm"/></remarks>
        public float ColorDiff_CIE1976(LAB aLab, LAB bLab)
        {
            var differences = Distance(aLab.L, bLab.L) + Distance(aLab.A, bLab.A) + Distance(aLab.B, bLab.B);
            return MathF.Sqrt(differences);
        }

        /// <summary>
        /// 计算色差
        /// </summary>
        /// <param name="Lab1"></param>
        /// <param name="Lab2"></param>
        /// <returns></returns>
        /// <remarks>
        /// <see cref="https://github.com/cr1st0ph3r/Forms.DeltaE2000/blob/main/Delta.cs"/></remarks>
        public static float ColorDiff_DeltaE2000(LAB Lab1, LAB Lab2)
        {
            float kL = 1.0f;
            float kC = 1.0f;
            float kH = 1.0f;
            float lBarPrime = (float)(0.5f * (Lab1.L + Lab2.L));
            float c1 = (float)MathF.Sqrt(Lab1.A * Lab1.A + Lab1.B * Lab1.B);
            float c2 = (float)MathF.Sqrt(Lab2.A * Lab2.A + Lab2.B * Lab2.B);
            float cBar = 0.5f * (c1 + c2);
            float cBar7 = cBar * cBar * cBar * cBar * cBar * cBar * cBar;
            float g = 0.5f * (1.0f - (float)MathF.Sqrt(cBar7 / (cBar7 + 6103515625.0F)));
            float a1Prime = (float)(Lab1.A * (1.0f + g));
            float a2Prime = (float)(Lab2.A * (1.0f + g));
            float c1Prime = (float)MathF.Sqrt(a1Prime * a1Prime + Lab1.B * Lab1.B);
            float c2Prime = (float)MathF.Sqrt(a2Prime * a2Prime + Lab2.B * Lab2.B);
            float cBarPrime = 0.5f * (c1Prime + c2Prime);
            float h1Prime = (float)((MathF.Atan2(Lab1.B, a1Prime) * 180.0) / Math.PI);
            float dhPrime;

            if (h1Prime < 0.0)
                h1Prime += 360.0f;
            float h2Prime = (float)((MathF.Atan2(Lab2.B, a2Prime) * 180.0) / Math.PI);
            if (h2Prime < 0.0)
                h2Prime += 360.0f;
            float hBarPrime = (float)((MathF.Abs(h1Prime - h2Prime) > 180.0f) ? (0.5f * (h1Prime + h2Prime + 360.0)) : (0.5 * (h1Prime + h2Prime)));
            float t = (float)(1.0f -
            0.17f * MathF.Cos(MathF.PI * (hBarPrime - 30.0f) / 180.0f) +
            0.24f * MathF.Cos(MathF.PI * (2.0f * hBarPrime) / 180.0f) +
            0.32f * MathF.Cos(MathF.PI * (3.0f * hBarPrime + 6.0f) / 180.0f) -
            0.20f * MathF.Cos(MathF.PI * (4.0f * hBarPrime - 63.0f) / 180.0f));
            if (MathF.Abs(h2Prime - h1Prime) <= 180.0)
                dhPrime = h2Prime - h1Prime;
            else
                dhPrime = (h2Prime <= h1Prime) ? (h2Prime - h1Prime + 360.0f) : (h2Prime - h1Prime - 360.0f);
            float dLPrime = (float)(Lab2.L - Lab1.L);
            float dCPrime = c2Prime - c1Prime;
            float dHPrime = (float)(2.0f * MathF.Sqrt(c1Prime * c2Prime) * MathF.Sin(MathF.PI * (0.5f * dhPrime) / 180.0f));
            float sL = (float)(1.0f + ((0.015f * (lBarPrime - 50.0f) * (lBarPrime - 50.0f)) / Math.Sqrt(20.0f + (lBarPrime - 50.0f) * (lBarPrime - 50.0f))));
            float sC = 1.0f + 0.045f * cBarPrime;
            float sH = 1.0f + 0.015f * cBarPrime * t;
            float dTheta = (float)(30.0f * MathF.Exp(-((hBarPrime - 275.0f) / 25.0f) * ((hBarPrime - 275.0f) / 25.0f)));
            float cBarPrime7 = cBarPrime * cBarPrime * cBarPrime * cBarPrime * cBarPrime * cBarPrime * cBarPrime;
            float rC = (float)(MathF.Sqrt(cBarPrime7 / (cBarPrime7 + 6103515625.0f)));
            float rT = (float)(-2.0f * rC * MathF.Sin(MathF.PI * (2.0f * dTheta) / 180.0f));
            return (float)(MathF.Sqrt(
                               (dLPrime / (kL * sL)) * (dLPrime / (kL * sL)) +
                               (dCPrime / (kC * sC)) * (dCPrime / (kC * sC)) +
                               (dHPrime / (kH * sH)) * (dHPrime / (kH * sH)) +
                               (dCPrime / (kC * sC)) * (dHPrime / (kH * sH)) * rT
                          )
             );
        }

        #region 颜色结构 
        public struct HSV
        {
            public HSV()
            {
                this.H = this.S = this.V = 0f;
            }
            public HSV(float h, float s, float v)
            {
                this.H = h;
                this.S = s;
                this.V = v;
            }


            /// <summary>
            /// 颜色偏角0-360
            /// </summary>
            public float H { get; set; }
            /// <summary>
            /// 0-1
            /// </summary>
            public float S { get; set; }
            /// <summary>
            /// 0-1
            /// </summary>
            public float V { get; set; }

            public RGB ToRGB()
            {
                float h = this.H;
                while (h < 0f) { h += 360f; };
                while (h >= 360f) { h -= 360f; };
                float R, G, B;
                if (V <= 0f)
                { R = G = B = 0f; }
                else if (S <= 0f)
                {
                    R = G = B = V;
                }
                else
                {
                    float hf = h / 60.0f;
                    int i = (int)Math.Floor(hf);
                    float f = hf - i;
                    float pv = V * (1f - S);
                    float qv = V * (1f - S * f);
                    float tv = V * (1f - S * (1f - f));
                    switch (i)
                    {

                        // Red is the dominant color

                        case 0:
                            R = V;
                            G = tv;
                            B = pv;
                            break;

                        // Green is the dominant color

                        case 1:
                            R = qv;
                            G = V;
                            B = pv;
                            break;
                        case 2:
                            R = pv;
                            G = V;
                            B = tv;
                            break;

                        // Blue is the dominant color

                        case 3:
                            R = pv;
                            G = qv;
                            B = V;
                            break;
                        case 4:
                            R = tv;
                            G = pv;
                            B = V;
                            break;

                        // Red is the dominant color

                        case 5:
                            R = V;
                            G = pv;
                            B = qv;
                            break;

                        // Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.

                        case 6:
                            R = V;
                            G = tv;
                            B = pv;
                            break;
                        case -1:
                            R = V;
                            G = pv;
                            B = qv;
                            break;

                        // The color is not defined, we should throw an error.

                        default:
                            //LFATAL("i Value error in Pixel conversion, Value is %d", i);
                            R = G = B = V; // Just pretend its black/white
                            break;
                    }
                }

                return new RGB(R, G, B);
            }
        }

        public struct RGB
        {
            public RGB()
            {
                this.R = 0F;
                this.G = 0F;
                this.B = 0F;
            }
            public RGB(float r, float g, float b)
            {
                this.R = r;
                this.G = g;
                this.B = b;
            }
            /// <summary>
            /// 0-1
            /// </summary>
            public float R { get; set; }
            /// <summary>
            /// 0-1
            /// </summary>
            public float G { get; set; }
            /// <summary>
            /// 0-1
            /// </summary>
            public float B { get; set; }

            public HSV ToHSV()
            {
                float max = MathF.Max(MathF.Max(R, G), B);
                float min = MathF.Min(MathF.Min(R, G), B);

                float V = max;
                float S = (max - min) / max;
                float H = 0f;
                if (max == min)
                {
                    S = 0;
                }
                else
                {
                    if (max == R)
                    {
                        H = (60 * (G - B) / (max - min));
                    }
                    else if (max == G)
                    {
                        H = (60 * (2 + (B - R) / (max - min)));
                    }
                    else if (max == B)
                    {
                        H = (60 * (4 + (R - G) / (max - min)));
                    }
                }
                if (H < 0) H += 360;


                return new HSV(H, S, V);
            }

            public RGB_BYTE ToRGB_BYTE()
            {
                return new RGB_BYTE(ToByte(R), ToByte(G), ToByte(B));
            }
            public void FromRGB_BYTE(RGB_BYTE rgb_byte)
            {
                var rgb = rgb_byte.ToRGB();
                this.R = rgb.R;
                this.G = rgb.G;
                this.B = rgb.B;
            }
            public int ToInt(byte alpha = 0xFF)
            {
                return ToRGB_BYTE().ToInt(alpha);
            }

            public void FromInt(int color)
            {
                RGB_BYTE rGB_BYTE = new RGB_BYTE();
                rGB_BYTE.FromInt(color);
                this.FromRGB_BYTE(rGB_BYTE);
            }

            public XYZ ToXYZ()
            {
                XYZ xyz = new XYZ();
                xyz.FromRGB(this);
                return xyz;
            }

            public LAB ToLAB()
            {
                XYZ xyz = this.ToXYZ();
                LAB lab = new LAB();
                lab.FromXYZ(xyz);
                return lab;

            }
            private static byte ToByte(float n)
            {
                return Convert.ToByte(Math.Min(255, Math.Max(0, (int)(n * 255))));
            }


        }

        public struct RGB_BYTE
        {
            public RGB_BYTE()
            {
                this.R = this.G = this.B = 0;
            }
            public RGB_BYTE(byte r, byte g, byte b)
            {
                this.R = r;
                this.G = g;
                this.B = b;
            }
            public byte R { get; set; }
            public byte G { get; set; }
            public byte B { get; set; }

            public RGB ToRGB()
            {
                return new RGB(R / 255f, G / 255f, B / 255f);
            }

            /// <summary>
            /// 转为32位整数形式
            /// </summary>
            /// <param name="alpha">透明度</param>
            /// <returns></returns>
            public int ToInt(byte alpha = 0xFF)
            {
                return (alpha << 24) | (R << 16) | (G << 8) | (B << 0);
            }

            public void FromInt(int color)
            {
                R = Convert.ToByte((color >> 16) & 0xFF);
                G = Convert.ToByte((color >> 8) & 0xFF);
                B = Convert.ToByte((color >> 0) & 0xFF);
            }
            public static implicit operator int(RGB_BYTE rgb_byte)
            {
                return rgb_byte.ToInt();
            }
            public static implicit operator RGB_BYTE(int color)
            {
                RGB_BYTE rgb_byte = new RGB_BYTE();
                rgb_byte.FromInt(color);
                return rgb_byte;
            }
        }

        /// <summary>
        /// LAB颜色
        /// </summary>
        public struct LAB
        { // Observer= 2°, Illuminant= D65
            private const float RefX = 95.047f;
            private const float RefY = 100.000f;
            private const float RefZ = 108.883f;
            public float L { get; set; }
            public float A { get; set; }

            public float B { get; set; }


            public XYZ ToXYZ()
            {
                var y = (this.L + 16) / 116.0f;
                var x = this.A / 500.0f + y;
                var z = y - this.B / 200.0f;

                y = MathF.Pow(y, 3) > 0.008856f ? MathF.Pow(y, 3) : (y - 16 / 116) / 7.787f;
                x = MathF.Pow(x, 3) > 0.008856f ? MathF.Pow(x, 3) : (x - 16 / 116) / 7.787f;
                z = MathF.Pow(z, 3) > 0.008856f ? MathF.Pow(z, 3) : (z - 16 / 116) / 7.787f;
                var xyz = new XYZ
                {
                    X = RefX * x,
                    Y = RefY * y,
                    Z = RefZ * z
                };
                return xyz;
            }
            public void FromXYZ(XYZ xyz)
            {
                var x = PivotXyz(xyz.X / RefX);
                var y = PivotXyz(xyz.Y / RefY);
                var z = PivotXyz(xyz.Z / RefZ);

                this.L = MathF.Max(0, 116 * y - 16);
                this.A = 500 * (x - y);
                this.B = 200 * (y - z);
            }
            public RGB ToRGB()
            {
                return this.ToXYZ().ToRGB();
            }

            public void FromRGB(RGB rgb)
            {
                this.FromXYZ(rgb.ToXYZ());
            }

            private static float PivotXyz(float n)
            {
                var i = CubicRoot(n);
                return n > 0.008856f ? i : 7.787f * n + 16 / 116;
            }

            private static float CubicRoot(float n)
            {
                return MathF.Pow(n, (1.0f / 3.0f));
            }



            public static implicit operator int(LAB lab)
            {
                return lab.ToRGB().ToRGB_BYTE().ToInt();
            }
            public static implicit operator LAB(int color)
            {
                LAB lab = new LAB();
                RGB rgb = new RGB();
                rgb.FromInt(color);
                lab.FromRGB(rgb);
                return lab;
            }
        }
        public struct XYZ
        {
            public float X { get; set; }
            public float Y { get; set; }
            public float Z { get; set; }

            public RGB ToRGB()
            {
                // (Observer = 2°, Illuminant = D65)
                var x = this.X / 100f;
                var y = this.Y / 100f;
                var z = this.Z / 100f;

                var r = x * 3.2406f + y * -1.5372f + z * -0.4986f;
                var g = x * -0.9689f + y * 1.8758f + z * 0.0415f;
                var b = x * 0.0557f + y * -0.2040f + z * 1.0570f;

                r = r > 0.0031308f ? 1.055f * MathF.Pow(r, 1 / 2.4f) - 0.055f : 12.92f * r;
                g = g > 0.0031308f ? 1.055f * MathF.Pow(g, 1 / 2.4f) - 0.055f : 12.92f * g;
                b = b > 0.0031308f ? 1.055f * MathF.Pow(b, 1 / 2.4f) - 0.055f : 12.92f * b;

                return new RGB(r, g, b);
            }

            public void FromRGB(RGB rgb)
            {
                var r = PivotRgb(rgb.R);
                var g = PivotRgb(rgb.G);
                var b = PivotRgb(rgb.B);

                this.X = r * 0.4124f + g * 0.3576f + b * 0.1805f;
                this.Y = r * 0.2126f + g * 0.7152f + b * 0.0722f;
                this.Z = r * 0.0193f + g * 0.1192f + b * 0.9505f;
            }

            private static float PivotRgb(float n)
            {
                return (n > 0.04045f ? MathF.Pow((n + 0.055f) / 1.055f, 2.4f) : n / 12.92f) * 100f;
            }

        }

        public struct CMYK
        {
            public float C { get; set; }
            public float M { get; set; }
            public float Y { get; set; }
            public float K { get; set; }

            public CMY ToCMY()
            {
                var cmy = new CMY
                {
                    C = (this.C * (1 - this.K) + this.K),
                    M = (this.M * (1 - this.K) + this.K),
                    Y = (this.Y * (1 - this.K) + this.K)
                };
                return cmy;
            }

            public void FromCMY(CMY cmy)
            {
                var k = 1.0f;
                if (cmy.C < k) k = cmy.C;
                if (cmy.M < k) k = cmy.M;
                if (cmy.Y < k) k = cmy.Y;
                this.K = k;

                if (BasicallyEqualTo(k, 1))
                {
                    this.C = 0;
                    this.M = 0;
                    this.Y = 0;
                }
                else
                {
                    this.C = (cmy.C - k) / (1 - k);
                    this.M = (cmy.M - k) / (1 - k);
                    this.Y = (cmy.Y - k) / (1 - k);
                }
            }

            private const float DefaultPrecision = .0001f;

            internal static bool BasicallyEqualTo(float a, float b)
            {
                return BasicallyEqualTo(a, b, DefaultPrecision);
            }

            internal static bool BasicallyEqualTo(float a, float b, float precision)
            {
                return MathF.Abs(a - b) <= precision;
            }
        }
        public struct CMY
        {
            public float C { get; set; }
            public float M { get; set; }
            public float Y { get; set; }

            public RGB ToRGB()
            {

                return new RGB
                {
                    R = (1f - this.C),
                    G = (1f - this.M),
                    B = (1f - this.Y)
                };
            }

            public void FromRGB(RGB rgb)
            {
                this.C = 1f - rgb.R;
                this.M = 1f - rgb.G;
                this.Y = 1f - rgb.B;
            }
        }
        #endregion
    }
}
